/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief FBO colorbuffer tests.
 *//*--------------------------------------------------------------------*/

#include "es31fFboColorbufferTests.hpp"
#include "es31fFboTestCase.hpp"
#include "es31fFboTestUtil.hpp"

#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"

#include "tcuCommandLine.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRGBA.hpp"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"

#include "sglrContextUtil.hpp"

#include "deRandom.hpp"
#include "deString.h"

#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::string;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::UVec4;
using tcu::TestLog;
using namespace FboTestUtil;

const tcu::RGBA MIN_THRESHOLD(12, 12, 12, 12);

static tcu::Vec4 generateRandomColor (de::Random& random)
{
	tcu::Vec4 retVal;

	retVal[0] = random.getFloat();
	retVal[1] = random.getFloat();
	retVal[2] = random.getFloat();
	retVal[3] = 1.0f;

	return retVal;
}

static tcu::CubeFace getCubeFaceFromNdx (int ndx)
{
	switch (ndx)
	{
		case 0:	return tcu::CUBEFACE_POSITIVE_X;
		case 1:	return tcu::CUBEFACE_NEGATIVE_X;
		case 2:	return tcu::CUBEFACE_POSITIVE_Y;
		case 3:	return tcu::CUBEFACE_NEGATIVE_Y;
		case 4:	return tcu::CUBEFACE_POSITIVE_Z;
		case 5:	return tcu::CUBEFACE_NEGATIVE_Z;
		default:
			DE_ASSERT(false);
			return tcu::CUBEFACE_LAST;
	}
}

class FboColorbufferCase : public FboTestCase
{
public:
	FboColorbufferCase (Context& context, const char* name, const char* desc, const deUint32 format)
		: FboTestCase	(context, name, desc)
		, m_format		(format)
	{
	}

	bool compare (const tcu::Surface& reference, const tcu::Surface& result)
	{
		const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_format), MIN_THRESHOLD));

		m_testCtx.getLog() << TestLog::Message << "Comparing images, threshold: " << threshold << TestLog::EndMessage;

		return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
	}

protected:
	const deUint32	m_format;
};

class FboColorTex2DCase : public FboColorbufferCase
{
	public:
		FboColorTex2DCase (Context& context, const char* name, const char* description, deUint32 texFmt, const IVec2& texSize)
			: FboColorbufferCase	(context, name, description, texFmt)
			, m_texFmt				(texFmt)
			, m_texSize				(texSize)
	{
	}

	protected:
		void preCheck (void)
		{
			checkFormatSupport(m_texFmt);
		}

		void render (tcu::Surface& dst)
		{
			tcu::TextureFormat		texFmt		= glu::mapGLInternalFormat(m_texFmt);
			tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(texFmt);

			Texture2DShader			texToFboShader	(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
			deUint32				texToFboShaderID = getCurrentContext()->createProgram(&texToFboShader);
			deUint32				fbo;
			deUint32				tex;

			// Setup shader
			texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);

			//  Generate fbo
			{
				glu::TransferFormat	transferFmt	= glu::getTransferFormat(texFmt);
				deUint32			format		= m_texFmt;
				const IVec2&		size		= m_texSize;

				glGenFramebuffers(1, &fbo);
				glGenTextures(1, &tex);

				glBindTexture(GL_TEXTURE_2D, tex);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D, 0, format, size.x(), size.y(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);

				glBindFramebuffer(GL_FRAMEBUFFER, fbo);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
				checkError();
				checkFramebufferStatus(GL_FRAMEBUFFER);
			}

			// Render texture to fbo
			{
				const deUint32		format		= GL_RGBA;
				const deUint32		dataType	= GL_UNSIGNED_BYTE;
				const int			texW		= 128;
				const int			texH		= 128;
				deUint32			tmpTex		= 0;
				const IVec2&		viewport	= m_texSize;
				tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

				tcu::fillWithComponentGradients(data.getAccess(), Vec4(0.0f), Vec4(1.0f));

				glGenTextures(1, &tmpTex);
				glBindTexture(GL_TEXTURE_2D, tmpTex);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

				glBindFramebuffer(GL_FRAMEBUFFER, fbo);
				glViewport(0, 0, viewport.x(), viewport.y());
				sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
			}

			readPixels(dst, 0, 0, getWidth(), getHeight(), texFmt, fmtInfo.lookupScale, fmtInfo.lookupBias);
			checkError();
		}

	private:
		deUint32	m_texFmt;
		IVec2		m_texSize;
};

class FboColorTexCubeArrayCase : public FboColorbufferCase
{
public:
	FboColorTexCubeArrayCase (Context& context, const char* name, const char* description, deUint32 texFmt, const IVec3& texSize)
		: FboColorbufferCase	(context, name, description, texFmt)
		, m_texSize				(texSize)
	{
		DE_ASSERT(texSize.z() % 6 == 0);
	}

protected:
	void preCheck (void)
	{
		auto ctxType = m_context.getRenderContext().getType();
		if (!glu::contextSupports(ctxType, glu::ApiType::core(4, 5)) &&
			!glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) &&
			!m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array"))
			TCU_THROW(NotSupportedError, "Test requires extension GL_EXT_texture_cube_map_array or a context version equal or higher than 3.2");

		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		TestLog&				log					= m_testCtx.getLog();
		de::Random				rnd					(deStringHash(getName()) ^ 0xed607a89 ^ m_testCtx.getCommandLine().getBaseSeed());
		tcu::TextureFormat		texFmt				= glu::mapGLInternalFormat(m_format);
		tcu::TextureFormatInfo	fmtInfo				= tcu::getTextureFormatInfo(texFmt);

		Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt), fmtInfo.valueMax-fmtInfo.valueMin, fmtInfo.valueMin);
		TextureCubeArrayShader	arrayTexShader		(glu::getSamplerCubeArrayType(texFmt), glu::TYPE_FLOAT_VEC4, glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType()));

		deUint32				texToFboShaderID	= getCurrentContext()->createProgram(&texToFboShader);
		deUint32				arrayTexShaderID	= getCurrentContext()->createProgram(&arrayTexShader);

		// Setup textures
		texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
		arrayTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);

		// Framebuffers.
		std::vector<deUint32>	fbos;
		deUint32				tex;

		{
			glu::TransferFormat	transferFmt		= glu::getTransferFormat(texFmt);
			bool				isFilterable	= glu::isGLInternalColorFormatFilterable(m_format);
			const IVec3&		size			= m_texSize;

			log << TestLog::Message
				<< "Creating a cube map array texture ("
				<< size.x() << "x" << size.y()
				<< ", depth: "
				<< size.z() << ")"
				<< TestLog::EndMessage;

			glGenTextures(1, &tex);

			glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY,	GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY,	GL_TEXTURE_MIN_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY,	GL_TEXTURE_MAG_FILTER,	isFilterable ? GL_LINEAR : GL_NEAREST);
			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_format, size.x(), size.y(), size.z(), 0, transferFmt.format, transferFmt.dataType, DE_NULL);

			log << TestLog::Message << "Creating a framebuffer object for each layer-face" << TestLog::EndMessage;

			// Generate an FBO for each layer-face
			for (int ndx = 0; ndx < m_texSize.z(); ndx++)
			{
				deUint32 layerFbo;

				glGenFramebuffers(1, &layerFbo);
				glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
				glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, ndx);
				checkError();
				checkFramebufferStatus(GL_FRAMEBUFFER);

				fbos.push_back(layerFbo);
			}
		}

		log << TestLog::Message << "Rendering test images to layer-faces in randomized order" << TestLog::EndMessage;

		{
			std::vector<int> order(fbos.size());

			for (size_t n = 0; n < order.size(); n++)
				order[n] = (int)n;
			rnd.shuffle(order.begin(), order.end());

			for (size_t ndx = 0; ndx < order.size(); ndx++)
			{
				const int			layerFace	= order[ndx];
				const deUint32		format		= GL_RGBA;
				const deUint32		dataType	= GL_UNSIGNED_BYTE;
				const int			texW		= 128;
				const int			texH		= 128;
				deUint32			tmpTex		= 0;
				const deUint32		fbo			= fbos[layerFace];
				const IVec3&		viewport	= m_texSize;
				tcu::TextureLevel	data		(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

				tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));

				glGenTextures(1, &tmpTex);
				glBindTexture(GL_TEXTURE_2D, tmpTex);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

				glBindFramebuffer(GL_FRAMEBUFFER, fbo);
				glViewport(0, 0, viewport.x(), viewport.y());
				sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
				checkError();

				// Render to framebuffer
				{
					const Vec3			p0		= Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
					const Vec3			p1		= p0 + Vec3(1.0f, 1.0f, 0.0f);
					const int			layer	= layerFace / 6;
					const tcu::CubeFace	face	= getCubeFaceFromNdx(layerFace % 6);

					glBindFramebuffer(GL_FRAMEBUFFER, 0);
					glViewport(0, 0, getWidth(), getHeight());

					glActiveTexture(GL_TEXTURE0);
					glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);

					arrayTexShader.setLayer(layer);
					arrayTexShader.setFace(face);
					arrayTexShader.setUniforms(*getCurrentContext(), arrayTexShaderID);

					sglr::drawQuad(*getCurrentContext(), arrayTexShaderID, p0, p1);
					checkError();
				}
			}
		}

		readPixels(dst, 0, 0, getWidth(), getHeight());
	}

private:
	IVec3 m_texSize;
};

FboColorTests::FboColorTests (Context& context)
	: TestCaseGroup(context, "color", "Colorbuffer tests")
{
}

FboColorTests::~FboColorTests (void)
{
}

void FboColorTests::init (void)
{
	static const deUint32 colorFormats[] =
	{
		// RGBA formats
		GL_RGBA32I,
		GL_RGBA32UI,
		GL_RGBA16I,
		GL_RGBA16UI,
		GL_RGBA8,
		GL_RGBA8I,
		GL_RGBA8UI,
		GL_SRGB8_ALPHA8,
		GL_RGB10_A2,
		GL_RGB10_A2UI,
		GL_RGBA4,
		GL_RGB5_A1,

		// RGB formats
		GL_RGB8,
		GL_RGB565,

		// RG formats
		GL_RG32I,
		GL_RG32UI,
		GL_RG16I,
		GL_RG16UI,
		GL_RG8,
		GL_RG8I,
		GL_RG8UI,

		// R formats
		GL_R32I,
		GL_R32UI,
		GL_R16I,
		GL_R16UI,
		GL_R8,
		GL_R8I,
		GL_R8UI,

		// GL_EXT_color_buffer_float
		GL_RGBA32F,
		GL_RGBA16F,
		GL_R11F_G11F_B10F,
		GL_RG32F,
		GL_RG16F,
		GL_R32F,
		GL_R16F,

		// GL_EXT_color_buffer_half_float
		GL_RGB16F
	};

	static const deUint32 unorm16ColorFormats[] =
	{
		GL_R16,
		GL_RG16,
		GL_RGBA16
	};

	// .texcubearray
	{
		tcu::TestCaseGroup* texCubeArrayGroup = new tcu::TestCaseGroup(m_testCtx, "texcubearray", "Cube map array texture tests");
		addChild(texCubeArrayGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
			texCubeArrayGroup->addChild(new FboColorTexCubeArrayCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
																	 colorFormats[fmtNdx], IVec3(128, 128, 12)));
	}

	// .tex2d
	{
		tcu::TestCaseGroup* tex2dGroup = new tcu::TestCaseGroup(m_testCtx, "tex2d", "Render to texture");
		addChild(tex2dGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(unorm16ColorFormats); ndx++)
			tex2dGroup->addChild(new FboColorTex2DCase(m_context, getFormatName(unorm16ColorFormats[ndx]), "", unorm16ColorFormats[ndx], IVec2(129, 117)));
	}
}

} // Functional
} // gles31
} // deqp
